﻿/*
 Add Reference
 COM / Microsoft Excel 15.0 Object Library
 COM / Microsoft Office 14.0 Object Library
 PIA ist historisch
 * 
 * 32 bit unbedingt weil ADO /ACE / OLE nur 32 bit
 * auf 64 bit entwicheln ist scheiße
 * BUILD / Configuration Manager / Active Solution Platform ==> x86
 * PROJECT / ___ Properties / Build / Platform target ==> x86
 * http://stackoverflow.com/questions/17716207/the-microsoft-ace-oledb-12-0-provider-is-not-registered-on-the-local-machine-w
 * 
 * 
 * TODO alle Spalten von der Eingabe übernehmen
 * 
 * 
 * TODO Spalten nur in Rules, nicht in Programm
 * 
 * 
 * 
 * TODO Raus mit ********Access ist Vorbedingung*******
 *  
 * http://www.microsoft.com/en-us/download/confirmation.aspx?id=23734#
 * AccessDatabaseEngine.exe           ist bei bei _Arbeitsplatz einrichten
 * 
 * SQL Server Compact http://www.microsoft.com/en-us/download/details.aspx?id=30709
 * How to: Deploy a SQL Server Compact 4.0 Database with ClickOnce
 * http://msdn.microsoft.com/en-us/library/aa983326(v=vs.110).aspx
 * 
 * SQL Lite wahrscheinlich umständlicher
 * 
 * 
 * TODO aufteilen
 * xls <--> db umwandeln
 * in db berechnen
 * 
 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Common;
using System.Data.OleDb;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Windows;
using Microsoft.Office.Interop.Excel;
//using Microsoft.Office.Interop.Access;
using System.Management;


/*
pubish ClickOnce auf Codeplex ist nur manuell
https://codeplex.codeplex.com/wikipage?title=ClickOnce
zip the publish directory, *.application must be at the root.
codeplex.com / ??? / publish clickonce / ??? / upload zip
*/


/***
 * 
 * MS Access wird ausschließlich über ADO (OLE?) angesprochen, also nicht über Interop
 * Excel Interop == Worksheet.Cell[row, column]
 * - Lesen/Schreiben der Regeln 
 * - Tabellenname für Daten lesen 
 * 
 * Lohnt sich der Umweg über die Access Datenbank, Kann ADO SQL auf xls?.
 * 
 * 
 * 
 */

namespace Zuordnung {
    public partial class MainWindow : System.Windows.Window {
        // table header Spaltennamen
        /* 
SID
Installation
Client
UserID
Developer?
License Type
Special Version
Surcharge
UserGroup
First Name
Last Name
LT Group
Cons.LT	Number	
Total Score
Share in %
Email
        */
        // todo die werte aus den regeln lesen
        static string thSID = "SID";
        static string thClient = "Client";
        static string thUserID = "UserID";
        static string thUG = "UserGroup";
        static string thfirstName = "FirstName"; // First Name
        static string thlastName = "LastName"; // Last Name
        static string thCLT = "CLT"; // Cons.LT
        static string thShare = "Share"; // Share in %

        static string th_all = thSID + "," + thClient + "," + thUserID + "," + thUG + "," + thfirstName + "," + thlastName + "," + thCLT + "," + thShare;

        static public int AnzahlDaten = 0;
        static public int AnzahlErgebnisse = 0;
        static public int AnzahlFehlendeZuordnungen = 0;
        static public int AnzahlKonflikte = 0;
        static public int AnzahlSyntaxfehler = 0;
        static public string FullFileNameDaten = "";
        static public string FullFileNameCompare = "";
        static public string SafeFileNameDaten = "";
        static public string FullFileNameRegeln = "";
        static public string SafeFileNameRegeln = "";
        static public Microsoft.Office.Interop.Excel.Application xlapp = null;
        static public string AusgabeDatenDateiName = "";
        static public string AusgabeStopwort = "AUSGABE_WIRD_ÜBERSCHRIEBEN";
        static public bool AusgabeErzeugen = true;

        static public string OptionaleAusgabespalte1 = "OptionaleAusgabespalte1";
        static public string OptionaleAusgabespalte2 = "OptionaleAusgabespalte2";
        static public string OptionaleAusgabespalte3 = "OptionaleAusgabespalte3";


        static OleDbConnection _dbconn = null;
        static OleDbConnection _exconn = null;
        private BackgroundWorker bgworker;
        private enum bgtask { bgDaten, bgRegeln, bgRegelnReCalc };

        public MainWindow() {
            try {

                Process[] pa1 = Process.GetProcessesByName("EXCEL");
                if (pa1.Length > 0) {
                    String n = pa1.Length > 1 ? "processes" : "process";
                    System.Windows.Forms.MessageBox.Show("You must close " + pa1.Length + " Excel " + n + " before you can use this application", "Zuordnung");
                    System.Windows.Application.Current.Shutdown();
                }

                Zuordnung.ExcelFabrik.open();
                xlapp = Zuordnung.ExcelFabrik.xlapp;
                InitializeComponent();

            } catch (Exception e) {
                System.Windows.Forms.MessageBox.Show(e.Message, "Main");
                throw (e);
            }
        }


        [STAThread]
        private void UserClicks_Regeln(object sender, System.Windows.RoutedEventArgs e) {
            bgwrk(bgtask.bgRegeln);
        }

        [STAThread]
        private void UserClicks_RegelnReCalc(object sender, System.Windows.RoutedEventArgs e) {
            bgwrk(bgtask.bgRegelnReCalc);
        }


        [STAThread]
        private void UserClicks_Daten(object sender, System.Windows.RoutedEventArgs e) {
            bgwrk(bgtask.bgDaten);
        }


        [STAThread]
        private void bgwrk(bgtask bgt) {

            bgworker = new BackgroundWorker();
            bgworker.WorkerReportsProgress = true;
            bgworker.WorkerSupportsCancellation = true;
            if (bgt != bgtask.bgRegelnReCalc) {
                System.Windows.Forms.OpenFileDialog fdia = new System.Windows.Forms.OpenFileDialog();
                fdia.Filter = "Excel|*.xlsx"; // xls auf Win7 gestrichen! todo  ;*.csv
                if (fdia.ShowDialog() != System.Windows.Forms.DialogResult.OK)
                    return; ///////////////////////////// KEINE DATEI GEWÄHLT

                if (fdia.SafeFileName.EndsWith(AusgabeStopwort + ".xls", StringComparison.InvariantCultureIgnoreCase)) {
                    MessageBox.Show("Sorry.  " + fdia.SafeFileName + "  ist eine Ausgabedatei.");
                    return;
                }
                // .XLS wird von ADO/Jet nicht gelesen, nur  .xls
                // todo Endung umschreiben 
                if (fdia.SafeFileName.EndsWith(".XLS", StringComparison.InvariantCulture)) {
                    MessageBox.Show("Sorry.  Die Endung von " + fdia.SafeFileName + "  muß exakt .xls lauten (also z.B. nicht .XLS).");
                    return;
                }
                if (bgt == bgtask.bgRegeln) {
                    FullFileNameRegeln = fdia.FileName;
                    SafeFileNameRegeln = fdia.SafeFileName;
                }
                if (bgt == bgtask.bgDaten) {
                    FullFileNameDaten = fdia.FileName;
                    SafeFileNameDaten = fdia.SafeFileName;
                    AusgabeDatenDateiName = fileext_Ausgabe_xls(fdia.FileName);
                }
            }

            // unnötig wenn Knöpfe (de)aktiviert
            if (bgt == bgtask.bgRegeln && _dbconn == null) {
                MessageBox.Show("Erst Daten wählen");
                return;
            }

            if (bgt == bgtask.bgRegeln) {
                _btnStartRegeln.IsEnabled = false;
                _btnCancelRegeln.IsEnabled = true;
            }
            if (bgt == bgtask.bgRegelnReCalc) {
                _btnReCalcRegeln.IsEnabled = false;
                _btnCancelRegeln.IsEnabled = true;
            }


            // GUI Elemente dürfen nicht von doWork angefaßt werden
            bgworker.DoWork += delegate(object myself, DoWorkEventArgs args) {
                if (bgt == bgtask.bgRegeln || bgt == bgtask.bgRegelnReCalc)
                    BerechneRegeln(myself as BackgroundWorker, args, FullFileNameRegeln);
                if (bgt == bgtask.bgDaten) {
                    ladeDaten(myself as BackgroundWorker, FullFileNameDaten);
                }
            };

            bgworker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args) {
                if (args.ProgressPercentage >= 0) {
                    statusprogressbar100.Visibility = Visibility.Visible;
                    statusprogressbar100.IsIndeterminate = false;
                    statusprogressbar100.Value = args.ProgressPercentage;
                } else {
                    statusprogressbar100.IsIndeterminate = true;
                    statusprogressbar100.Visibility = Visibility.Visible;
                }
                statustext.Text = args.UserState.ToString();
            };


            /*
             * Ende der Bearbeitung.
             * Auch Abbruch Cancel landen hier.
             * 
             * Das delegate sieht zwar Scheiße aus, letzlich ist es eine Methode
             */
            bgworker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args) {
                if (args.Error != null) {
                    System.Windows.Forms.MessageBox.Show(args.Error.Message, "Background message");
                }


                statusprogressbar100.Visibility = Visibility.Hidden;
                statustext.Text = "";
                if (bgt == bgtask.bgRegeln) {
                    textBoxZuordnungfile.Text = SafeFileNameRegeln;
                }
                if (bgt == bgtask.bgRegeln || bgt == bgtask.bgRegelnReCalc) {
                    _btnStartRegeln.IsEnabled = true;
                    _btnReCalcRegeln.IsEnabled = true;
                    _btnCancelRegeln.IsEnabled = false;
                    textBoxFehlendeZuordnungen.Text = i2s(AnzahlFehlendeZuordnungen);
                    textBoxKonflikte.Text = i2s(AnzahlKonflikte);
                    textBoxSyntaxfehler.Text = i2s(AnzahlSyntaxfehler);
                }
                if (bgt == bgtask.bgDaten) {
                    _btnStartRegeln.IsEnabled = true;
                    textBoxZeilen.Text = i2s(AnzahlDaten);
                    if (AnzahlDaten > 0)
                        textBoxDatenfile.Text = SafeFileNameDaten;
                }
            };


            // warum zum Schluß?
            // warum überhaupt?
            bgworker.RunWorkerAsync();
        }

        private void UserCancels(object sender, System.Windows.RoutedEventArgs e) {
            bgworker.CancelAsync();
        }

        private void User_Select_Ausgabe(object sender, RoutedEventArgs e) {
            AusgabeErzeugen = true;
        }


        private void User_DeSelect_Ausgabe(object sender, RoutedEventArgs e) {
            AusgabeErzeugen = false;
        }

        public static string fileext_change_xls_mdb(string x) {
            if (x.EndsWith(".xls")) return x.Substring(0, x.Length - 3) + "mdb";
            if (x.EndsWith(".xlsx")) return x.Substring(0, x.Length - 4) + "mdb";

            throw new Exception("muß xls oder xlsx sein: " + x);
        }

        public static string fileext_Ausgabe_xls(string x) {
            return x.Replace(".xls", "." + AusgabeStopwort + ".xls");
        }
        public static string i2s(int i) {
            return i.ToString("N0", CultureInfo.CreateSpecificCulture("de-DE"));
        }


        /// <summary>
        /// Gute Performance
        /// Problem: eine Zahl in einem Textfeld wird ignoriert (SELECT INTO und INSERT INTO, Weid CLT 75)
        /// Workaround: IMEX=1 http://support.microsoft.com/kb/194124/EN-US/
        /// 
        /// 
        /// </summary>
        public static void ladeDaten(BackgroundWorker worker, string fname) {
            bool dbcreated = false;
            if (!File.Exists(fileext_change_xls_mdb(fname))) {
                // legt eine leere Datenbank an. Eingebettete Resource statt ADOX.Create()
                System.Reflection.Assembly objAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                Stream objStream = objAssembly.GetManifestResourceStream("Zuordnung.Datenbank1.mdb");
                byte[] tmpBytes = new Byte[objStream.Length];
                objStream.Read(tmpBytes, 0, (int)objStream.Length);
                FileStream objFileStream = new FileStream(fileext_change_xls_mdb(fname), FileMode.Create);
                objFileStream.Write(tmpBytes, 0, (int)objStream.Length);
                objFileStream.Close();
                dbcreated = true;
            }

            /***
             * The Microsoft OLE DB Provider for Jet and the Jet ODBC driver are available in 32-bit versions only. You can't run them in 64 bit mode. 
             * http://www.connectionstrings.com/Articles/Show/using-jet-in-64-bit-environments
             * 
             * Microsoft.ACE.OLEDB.12.0
             * ist Access 2010 http://www.microsoft.com/en-us/download/details.aspx?id=13255
             * Not a replacement for Jet
             * If you need a general replacement for Jet you should use SQL Server Express Edition
             * 
             * Microsoft.Jet.OLEDB.4.0 
             * ist deprecated??
             */
            String ACCESSprovider = "Microsoft.ACE.OLEDB.12.0";

            String DbconnectionString = "Provider=" + ACCESSprovider + ";Data Source='" + fileext_change_xls_mdb(fname) + "'";
            String ExconnectionString = "Provider=" + ACCESSprovider + ";Data Source='" + fname + "';Extended Properties=\"Excel 8.0;HDR=YES;\"";

            _dbconn = new OleDbConnection(DbconnectionString);
            _exconn = new OleDbConnection(ExconnectionString);


            try {
                _dbconn.Open();
            } catch (System.InvalidOperationException e) {
                System.Windows.Forms.MessageBox.Show(e.Message + "\n\n\n              QUIT NOW!!!", "Zuordnung hat ein Problem");
                return;
            }


            FileInfo infoMDB = new FileInfo(fileext_change_xls_mdb(fname));
            bool versteckeMDB = false;
            if (versteckeMDB) {
                infoMDB.Attributes = FileAttributes.Hidden;
            }

            FileInfo infoXLS = new FileInfo(fname);
            bool Datenbankzugriff_und_Zeitstempel_verstanden = false;
            /**
             * Um das Problem zu lösen:
             * - Doku lesen
             * - Googeln
             * - nach dem Schreiben der mdb Zeitstempel prüfen.
             * - Nur wenn Zeitstempel gültig, stehen lassen.
             * 
             */
            if (Datenbankzugriff_und_Zeitstempel_verstanden) {
                if (!dbcreated && (infoMDB.LastWriteTime >= infoXLS.LastWriteTime)) {
                    AnzahlDaten = SelectOneInt("SELECT count(*) FROM Zoo");
                    return;  //////////////////////////////////////////////// DATEN werden AUS MDB GEZOGEN ///////////////////////////////////
                    // todo Laden sollte optional möglich sein
                }
            }


            /*
             * http://127.0.0.1:47873/help/0-3684/ms.help?method=page&id=B0788993-E62D-4F68-8235-5F87B1D48525&product=VS&productVersion=100&topicVersion=100&locale=EN-US&topicLocale=EN-US&embedded=true
             *  I8N
             */
            worker.ReportProgress(-1, "Öffne Excel");
            String trySQL = "drop table Zoo";
            try {
                if (Markus.OleDb.listOfTables(_dbconn).Contains("Zoo")) {
                    using (OleDbCommand command = new OleDbCommand(trySQL, _dbconn)) {
                        command.ExecuteNonQuery();
                    }
                }
            } catch (Exception e) {
                Exception ee = new Exception("Command was " + trySQL + "\n" + e.Message, e);
                throw (ee);
            }

            /// Damit Excel von ADO gelesen werden kann, muß 
            ///  - die Namen der Arbeitsblätter bekannt sein 

            List<string> TabNamen = new List<string>();
            {
                ExcelReader001 xr = new ExcelReader001(fname);

                foreach (String xlws in xr.workSheetNames) {
                    if (xlws.StartsWith("Pivot")) { continue; }
                    TabNamen.Add(xlws);
                }


            }
            bool useOleForRules = true;
            if (useOleForRules) {
                _exconn.Open();
                List<string> TabNamen2 = Markus.OleDb.listOfTables(_exconn);
                _exconn.Close();
            }




            // Problem SINGLE/FLOAT4 erzeugt Rundungsfehler (2,0200000089) beim Speichern mit INSERT INTO
            // Lösung NUMBER/FLOAT8  benutzen, nicht versuchen Speicherplatz zu sparen.
            // Hintergrund Share ist 0-100 und würde in 4 Bytes passen.
            //
            // Problem Textwerte in CHAR(32) werden auf 32 Zeichen mit Leerzeichen gepadded.
            // Lösung VARCHAR
            // 
            // learn WITH COMPRESSION hat keine Wirkung
            ExecuteNonQuery("CREATE TABLE Zoo");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN " + thSID + " CHAR(3)");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN " + thClient + " SHORT");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN " + thUserID + " VARCHAR");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN " + thUG + " VARCHAR");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN " + thfirstName + " VARCHAR");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN " + thlastName + " VARCHAR");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN " + thCLT + " CHAR(2)");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN " + thShare + " NUMBER");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN EXTAB VARCHAR");

            for (int i = 0; i < TabNamen.Count; i++) {
                worker.ReportProgress(-1, "Öffne Excel (Blatt " + TabNamen[i] + ")");
                AnzahlDaten += insertWorksheetIntoZoo(fname, TabNamen[i]);
            }
            /// 
            worker.ReportProgress(-1, "Öffne Excel (Nachbereitung)");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN Reiter VARCHAR");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN optaus1 VARCHAR");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN optaus2 VARCHAR");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN optaus3 VARCHAR");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN Query VARCHAR");
            worker.ReportProgress(-1, "Öffne Excel (Nachbereitung index SID)");
            ExecuteNonQuery("CREATE INDEX IdxSID ON Zoo (" + thSID + ")");
            worker.ReportProgress(-1, "Öffne Excel (Nachbereitung index Client)");
            ExecuteNonQuery("CREATE INDEX IdxClient ON Zoo (" + thClient + ")");
            worker.ReportProgress(-1, "Öffne Excel (Nachbereitung index UG)");
            ExecuteNonQuery("CREATE INDEX IdxUG ON Zoo (" + thUG + ")");
            worker.ReportProgress(-1, "Öffne Excel (Nachbereitung index CLT)");
            ExecuteNonQuery("CREATE INDEX IdxCLT ON Zoo (" + thCLT + ")");
        }

        /// <summary>
        /// laden zu umständlich?
        /// </summary>
        /// <param name="fname"></param>
        /// <param name="tabname"></param>
        /// <returns></returns>


        /*
         * 
         * Ich mache hier ein komisches INSERT.
         * Welche Alternativen gibt es?
         * +++++++++ DataTable aus Exceltabelle.
         * 
         * Die explizite Benennung der Spalten ist mühsam.
         */
        private static int insertWorksheetIntoZoo(string fname, string worksheet) {
            try {
                return ExecuteNonQuerySELTSAM("INSERT INTO Zoo (" + th_all + ", EXTAB) SELECT " + th_all + ", '" + worksheet + "' FROM [" + worksheet + "$] IN '" + fname + "' 'EXCEL 8.0;HDR=YES;IMEX=1'");
            } catch (Exception e) {
                if (e.Message.Equals("Für mindestens einen erforderlichen Parameter wurde kein Wert angegeben.") ||
                    e.Message.Equals("No value given for one or more required parameters.")) {
                    MessageBox.Show("Das Arbeitsblatt " + worksheet + " wird übergangen weil eine der Spalten " + th_all +
                        " fehlt. \nSie müssen die Spalten umbenennen.\n\nFirstName <-- First Name\nLastName <-- Last Name\nCLT <-- Cons.LT\nShare <-- Share in %\n", "Zuordnung - FEHLER");
                    return 0;
                } else {
                    throw (e);
                }
            }
        }

        /// <summary>
        /// Die Berechnung der Zuordnung
        ///         
        /// </summary>
        /// <param name="xlwb"></param>
        public static void BerechneRegeln(BackgroundWorker worker, DoWorkEventArgs args, string ffname) {
            worker.ReportProgress(-1, "Vorbereitung der Datenbank");
            AnzahlErgebnisse = 0;
            AnzahlKonflikte = 0;
            AnzahlSyntaxfehler = 0;

            Workbook xlwb = xlapp.Workbooks.Open(ffname);
            Worksheet wsdata = null;

            // Betrachte und identifiziere die worksheets der Regeln.
            switch (xlwb.Worksheets.Count) {
                case 1:
                    wsdata = xlwb.Worksheets[1]; // knowhow Worksheets sind 1-basiertes Array
                    break;
                case 2:
                    int data = 1, todos = 2;
                    if (xlwb.Worksheets[1].Name.Equals("TODOS")) {
                        data = 2;
                        todos = 1;
                    }
                    wsdata = xlwb.Worksheets[data];
                    xlapp.DisplayAlerts = false;
                    xlwb.Worksheets[todos].Delete();
                    xlapp.DisplayAlerts = true;
                    break;
                default:
                    MessageBox.Show("Regeln dürfen höchstens zwei Arbeitsblätter enthalten, wovon das Blatt TODOS jedesmal erneuert wird.");
                    return;
            }

            String sid, client, ug, clt, Reiter, optaus1, optaus2, optaus3;

            // learn: Für Löschen von Spalten ist UPDATE T SET C=NULL nicht performant.
            // learn: Für DROP COLUMN darf kein Index vorhanden sein.
            try {
                ExecuteNonQuerySELTSAM("DROP INDEX  IdxReiter ON Zoo");
            } catch (Exception e) {
                int i = e.Message.Length;
            }
            // warum nicht ganz neu?
            ExecuteNonQuery("ALTER TABLE Zoo DROP COLUMN Reiter");
            ExecuteNonQuery("ALTER TABLE Zoo DROP COLUMN optaus1");
            ExecuteNonQuery("ALTER TABLE Zoo DROP COLUMN optaus2");
            ExecuteNonQuery("ALTER TABLE Zoo DROP COLUMN optaus3");
            ExecuteNonQuery("ALTER TABLE Zoo DROP COLUMN Query");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN Reiter VARCHAR");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN optaus1 VARCHAR");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN optaus2 VARCHAR");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN optaus3 VARCHAR");
            ExecuteNonQuery("ALTER TABLE Zoo ADD COLUMN Query CHAR");
            ExecuteNonQuery("CREATE INDEX IdxReiter ON Zoo (Reiter)");

            //Sammele die optionalen Ausgabespalten ein
            if (!String.IsNullOrWhiteSpace(wsdata.Cells[1, 6].Text)) OptionaleAusgabespalte1 = wsdata.Cells[1, 6].Text;
            if (!String.IsNullOrWhiteSpace(wsdata.Cells[1, 7].Text)) OptionaleAusgabespalte2 = wsdata.Cells[1, 7].Text;
            if (!String.IsNullOrWhiteSpace(wsdata.Cells[1, 8].Text)) OptionaleAusgabespalte3 = wsdata.Cells[1, 8].Text;

            // Schreibe in die Regeldatei
            wsdata.Cells.set_Item(1, 9, "Feedback zu den Regeln");

            int maxrow = (wsdata.get_Range("A1").get_End(XlDirection.xlDown)).Row;
            for (int row = 2; row <= maxrow; row++) {
                if (worker.CancellationPending) {
                    AnzahlKonflikte = 0;
                    AnzahlErgebnisse = 0;
                    args.Cancel = true;
                    xlwb.Close(SaveChanges: false);
                    return;
                }
                int percentComplete = (int)((float)row / (float)maxrow * 100);
                worker.ReportProgress(percentComplete, "Berechnung");

                //clt = xlws.Cells.get_Item(row, 4); // bad know how: Der System.__ComObject-Typ kann nicht implizit in string konvertiert werden
                //clt = xlws.Cells[row, 4].Value2; // bad know how: Cells[row, col].Value2 sieht 81 als Double (81.0)
                //clt = xlws.Cells[row, 4].Value; // bad know how: Cells[row, col].Value sieht 81 als Double (81.0)
                sid = wsdata.Cells[row, 1].Text;
                client = wsdata.Cells[row, 2].Text;
                ug = wsdata.Cells[row, 3].Text;
                clt = wsdata.Cells[row, 4].Text;
                Reiter = wsdata.Cells[row, 5].Text;
                optaus1 = wsdata.Cells[row, 6].Text;
                optaus2 = wsdata.Cells[row, 7].Text;
                optaus3 = wsdata.Cells[row, 8].Text;
                try {
                    int result = BerechneRegel(sid, client, ug, clt, Reiter, optaus1, optaus2, optaus3);
                    if (result < 0)
                        AnzahlKonflikte += -result;
                    else
                        AnzahlErgebnisse += result;
                    wsdata.Cells.set_Item(row, 9, result);
                } catch (System.Data.OleDb.OleDbException e) {
                    if (e.Message.StartsWith("Syntax")) {
                        AnzahlSyntaxfehler += 1;
                        wsdata.Cells.set_Item(row, 9, "Syntaxfehler");
                    } else {
                        throw (e);
                    }
                }
                //wsdata.Cells.set_Item(row, 9, WhereClause(sid, client, ug, clt));
            }
            wsdata = null;
            //xlwb.Save();
            xlwb.Close(SaveChanges: true); //todo offen lassen?
            //System.Runtime.InteropServices.Marshal.ReleaseComObject(xlwb);
            AnzahlFehlendeZuordnungen = AnzahlDaten - AnzahlErgebnisse;

            // Dokumentiere Regeln die für Zuordnung fehlen.
            worker.ReportProgress(-1, "Berechnung (TODOS)");
            string q;
            q = "SELECT SID, client, usergroup, clt, COUNT(*) AS Anzahl INTO [TODOS] IN '" + ffname + "' 'EXCEL 8.0;HDR=YES' FROM Zoo WHERE reiter is null GROUP BY SID, client, usergroup, clt  ORDER BY SID, client, usergroup, clt";
            // learn: INSERT INTO, HDR=YES benötigt genau die Spaltennamen in Excel            
            //q = "INSERT INTO [TODOS$] (SID, client, usergroup, clt, Anzahl) IN '" + ffname + "' 'EXCEL 8.0;HDR=YES' SELECT SID, client, usergroup, clt, COUNT(*) FROM Zoo where reiter is null GROUP BY SID, client, usergroup, clt  ORDER BY SID, client, usergroup, clt";
            ExecuteNonQuery(q);


            /////////////////////////// ES FOLGT: Ausgabe der Daten            
            /////////////////////////// ES FOLGT: Ausgabe der Daten            
            /////////////////////////// ES FOLGT: Ausgabe der Daten            
            /////////////////////////// ES FOLGT: Ausgabe der Daten            
            /////////////////////////// ES FOLGT: Ausgabe der Daten            
            /////////////////////////// ES FOLGT: Ausgabe der Daten            
            /////////////////////////// ES FOLGT: Ausgabe der Daten            
            /////////////////////////// ES FOLGT: Ausgabe der Daten            

            if (!AusgabeErzeugen)
                return; ///////////////////////////////////////// END OF PROCEDURE

            File.Delete(AusgabeDatenDateiName);
            String ExcelSorte = "";
            if (AusgabeDatenDateiName.EndsWith(".xls")) ExcelSorte = "EXCEL 8.0";
            if (AusgabeDatenDateiName.EndsWith(".xlsx")) ExcelSorte = "EXCEL 12.0 xml"; // wertvoll


            using (DbCommand command = _dbconn.CreateCommand()) {
                command.CommandText = "SELECT DISTINCT EXTAB FROM Zoo";
                using (DbDataReader dbr = command.ExecuteReader()) {
                    while (dbr.Read()) {
                        string extab = dbr["EXTAB"].ToString(); // Trim war nötig bis Wechsel von CHAR(32) auf VARCHAR
                        worker.ReportProgress(-1, "Ausgabe (" + extab + ")");
                        q = "SELECT " + th_all + ", Reiter, optaus1 as "
                            + OptionaleAusgabespalte1 + ", optaus2 as "
                            + OptionaleAusgabespalte2 + ", optaus3 as "
                            + OptionaleAusgabespalte3 + ", Query INTO [" + extab + "] IN '" + AusgabeDatenDateiName + "' '" + ExcelSorte + ";HDR=YES' FROM Zoo WHERE EXTAB = '" + extab + "'";
                        ExecuteNonQuery(q);
                    }
                }
            }
        }


        /// <summary>
        /// Berechne den Ziel-Reiter
        /// Dokumentiere das UPDATE Statemement als Kommentar zur Zeile
        /// Vorbedingung: Reiter der Zeilen sind leer.
        /// Seiteneffekt: Reiter in Datebankzeilen
        /// Ausnahmebehandlung: Kommentar in der Zeile
        /// </summary>
        /// <param name="Reiter"></param>
        /// <param name="sid"></param>
        /// <param name="client"></param>
        /// <param name="ug"></param>
        /// <param name="clt"></param>
        /// <returns>Anzahl der geänderten Zeilen wenn >=0, die der Zeilen mit Konflikten sonst.</returns>
        public static int BerechneRegel(string sid, string client, string ug, string clt, String Reiter, String optaus1, String optaus2, String optaus3) {
            string w = WhereClause(sid, client, ug, clt);
            int Konflikte = SelectOneInt("SELECT COUNT(Reiter) FROM Zoo" + w + " AND Reiter IS NOT NULL");
            if (Konflikte == 0) {
                String dieWhereClauseAlsKommentar = w.Replace("\"", "");
                if (dieWhereClauseAlsKommentar.Length > 6)
                    dieWhereClauseAlsKommentar = "because" + dieWhereClauseAlsKommentar.Substring(6);
                return ExecuteNonQuery("UPDATE Zoo SET Reiter=\"" + Reiter
                    + "\", optaus1=\"" + optaus1
                    + "\", optaus2=\"" + optaus2
                    + "\", optaus3=\"" + optaus3
                    + "\", Query=\"" + dieWhereClauseAlsKommentar + "\"" + w);
            } else {
                ExecuteNonQuery("UPDATE Zoo SET Query=\" !!! Regel für Reiter " + Reiter + " muß vorgezogen werden !!!\"" + w + " AND Reiter IS NOT NULL");
                return -Konflikte;
            }
        }


        ///
        /// Aus den Suchwerten wird eine WHERE clause erstellt. 
        /// <param name="sid"> Pflichtwert, exakter Vergleich</param> 
        /// <param name="client">Pflichtwert (kann sein "else" oder "all"), exakter Vergleich</param>
        /// <param name="ug">Pflichtwert (kann sein "else" oder "all"), Mustersuche mit "*"</param>
        /// <param name="clt">Pflichtwert (kann sein "else" oder "all"), exakter Vergleich</param>
        ///
        public static String WhereClause(string sid, string client, string ug, string clt) {
            //know how: Wildcard ist zwar '*' in Access aber '%' in Access angesteuert über .Net
            // idea where raus
            string wc = " where";
            bool EinElse = false;
            switch (sid) {
                case "all": /* noop */ // todo Syntax Error auslösen
                    break;
                default:
                    wc += " SID = \"" + sid + "\""; ;
                    break;
            }
            switch (client) {
                case "else": EinElse = true; break;
                case "all": /* noop */     break;
                default: wc += " and Client=" + client; break;
            }
            switch (ug) {
                case "else": EinElse = true; break;
                case "all": /* noop */ break;
                default:
                    // learn: Ohne * bzw % findet LIKE auch die exakten Matches nicht.
                    if (ug.Contains("*")) {
                        // learn: _ muß in LIKE escaped werden
                        // BAUSTELLE
                        // http://office.microsoft.com/en-ca/access-help/examples-of-using-wildcard-characters-adp-HP003083937.aspx
                        // ODBC http://msdn.microsoft.com/en-us/library/ms710128%28VS.85%29.aspx
                        /*
                         * todo der Unterstrich ist ein Wildchard und muß escaped werden.
                         * Versuche ich das, erhalte ich den Fehler
                            System.Data.OleDb.OleDbException wurde nicht von Benutzercode behandelt.
                            Message=Fehler E_FAIL(0x80004005) in IErrorInfo.GetDescription.
                            Source=System.Data
                            ErrorCode=-2147467259

                        */
                        bool escape_underscore = false;
                        if (escape_underscore && ug.Contains("_")) {
                            wc += " and " + thUG + " LIKE \"" + ug.Replace("*", "%").Replace("_", "!_") + "\" ESCAPE '!'";
                            //q += " and " + thUG + " LIKE \"" + ug.Replace("*", "%").Replace("_", "[_]") + "\"";
                        } else {
                            wc += " and " + thUG + " LIKE \"" + ug.Replace("*", "%") + "\"";
                        }
                    } else {
                        wc += " and " + thUG + " = \"" + ug + "\"";
                    }
                    break;
            }
            switch (clt) {
                case "else": EinElse = true; break;
                case "all": /* noop */ break;
                default: wc += " and CLT = \"" + clt + "\""; break;
            }

            if (EinElse) {
                wc += " and Reiter is null"; // Bestehende Werte müssen erhalten bleiben.
            }
            return wc;
        }

        /// <summary>
        /// Schreibt SQL mit ins Fenster
        /// </summary>
        /// <param name="q"></param>
        /// <returns></returns>
        private static int ExecuteNonQuery(string q) {
            OleDbCommand command = new OleDbCommand(q, _dbconn);
            int result = 0;
            try {
                result = command.ExecuteNonQuery();
                command.Dispose();
            } catch (Exception e) {
                _dbconn.Close();
                MessageBox.Show(q, e.Message);
                throw (e);
            }
            return result;
        }

        /// <summary>
        /// macht nichts
        /// </summary>
        /// <param name="q"></param>
        /// <returns></returns>
        private static int ExecuteNonQueryNOMSG(string q) {
            OleDbCommand command = new OleDbCommand(q, _dbconn);
            int result = 0;
            try {
                result = command.ExecuteNonQuery();
                command.Dispose();
            } catch (Exception e) {
                _dbconn.Close();
                throw (e);
            }
            return result;
        }

        /// <summary>
        /// macht auch nichts todo zusammenführen
        /// </summary>
        /// <param name="q"></param>
        /// <returns></returns>
        private static int ExecuteNonQuerySELTSAM(string q) {
            OleDbCommand command = new OleDbCommand(q, _dbconn);
            int result = 0;
            result = command.ExecuteNonQuery();
            command.Dispose();
            return result;
        }

        /// <summary>
        /// Eingabe: SELECT das Textwert zurückliefert.
        /// Ausgabe: Textwert(e) werden aufgenommen und hintereinandergeschrieben, getrennt mit Leerzeichen
        /// </summary>
        /// <param name="queryString"></param>
        /// <returns></returns>
        public static String SelectString(string queryString) {
            String r = "";
            using (OleDbCommand command = new OleDbCommand("queryString", _dbconn)) {
                // Idee: nur ein Command und Reader für das Programm. 
                // Vorteil: weniger Konstruktoren
                // Nachteil: wie räume ich die im Ausnahemfall ab?
                // Ich habe zur Zeit auch keine Ausnahmebehandlung
                command.CommandText = queryString;
                using (OleDbDataReader reader = command.ExecuteReader()) {
                    while (reader.Read()) {
                        r += " " + reader.GetString(0);
                    }
                    reader.Close(); // always call Close when done reading.
                }
            }
            return r;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="queryString"></param>
        /// <returns></returns>
        public static int SelectOneInt(string queryString) {
            using (OleDbCommand command = new OleDbCommand(queryString, _dbconn)) {
                using (OleDbDataReader reader = command.ExecuteReader()) {
                    reader.Read();
                    int i = reader.GetInt32(0);
                    reader.Close();
                    return i;
                }
            }
        }

        /// <summary>
        /// 
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_Closing(object sender, CancelEventArgs e) {
            Abräumen();
        }

        private void Abräumen() {
            if (_dbconn != null) {
                _dbconn.Close();
            }
            // todo unsauber
            if (bgworker != null) {
                bgworker.CancelAsync();
                bgworker.Dispose();
            }
            //MessageBox.Show(Zuordnung.ExcelFabrik.quit());
            if (xlapp != null)
                Zuordnung.ExcelFabrik.quit();
        }

    }



}
